//
// Created by zlf on 24-1-4.
//
#include "map_decoder/map_decoder_impl.h"

#include "map_decoder/map_decoder.h"

namespace autodrive {
namespace perception {

MapDecoderImpl::MapDecoderImpl() : is_init_{false} {}

MapDecoderImpl::~MapDecoderImpl() {}

void MapDecoderImpl::Init(MapDecoder::MapDecoderParameter const& para) noexcept(
    false) {
  parameter_ = para;

  net_width_ = static_cast<std::uint32_t>(
      (para.net_range.at(3) - para.net_range.at(0)) /
          (para.voxel_size.at(0) + EPS) / para.down_size +
      0.5f);
  net_height_ = static_cast<std::uint32_t>(
      (para.net_range.at(4) - para.net_range.at(1)) /
          (para.voxel_size.at(1) + EPS) / para.down_size +
      0.5f);

  detection_width_lower_bound_ = static_cast<std::uint32_t>(
      (para.detection_range.at(0) - para.net_range.at(0)) /
          (para.voxel_size.at(0) + EPS) / para.down_size +
      0.5f);
  detection_width_upper_bound_ = static_cast<std::uint32_t>(
      (para.detection_range.at(3) - para.net_range.at(0)) /
          (para.voxel_size.at(0) + EPS) / para.down_size +
      0.5f);
  detection_height_lower_bound_ = static_cast<std::uint32_t>(
      (para.detection_range.at(1) - para.net_range.at(1)) /
          (para.voxel_size.at(1) + EPS) / para.down_size +
      0.5f);
  detection_height_upper_bound_ = static_cast<std::uint32_t>(
      (para.detection_range.at(4) - para.net_range.at(1)) /
          (para.voxel_size.at(1) + EPS) / para.down_size +
      0.5f);

  dpuout_vec_.resize(2 * para.preds_number * net_width_ * net_height_);
  dpuout_vec_.assign(dpuout_vec_.size(), -1.0f);
  dpuout_xy_.resize(4 * para.preds_number * net_width_ * net_height_);
  dpuout_xy_.assign(dpuout_xy_.size(), -1.0f);
  visited_.resize(para.preds_number * net_width_ * net_height_);
  visited_.assign(visited_.size(), 0U);

  is_init_ = true;
}

void MapDecoderImpl::Clear() noexcept {
  dpuout_vec_.assign(dpuout_vec_.size(), -1.0f);
  dpuout_xy_.assign(dpuout_xy_.size(), -1.0f);
  visited_.assign(visited_.size(), false);
}

template <typename T>
void MapDecoderImpl::MapDecode(T const* dpuout_cls, T const* dpuout_reg,
                               std::uint8_t cls, std::uint8_t cls_scale,
                               std::uint8_t reg_scale,
                               Map& map) noexcept(false) {
  // Input check
  if (nullptr == dpuout_cls || nullptr == dpuout_reg) {
    throw std::runtime_error("Input is null pointer");
    return;
  }

  if (cls == 0 || cls == 2 || cls == 4 || cls == 5) {
    decode_process<T>(dpuout_cls, dpuout_reg, cls, cls_scale, reg_scale,
                      DecodeMode::POLYLINE, map);
  } else if (cls == 3 || cls == 6 || cls == 7) {
    decode_process<T>(dpuout_cls, dpuout_reg, cls, cls_scale, reg_scale,
                      DecodeMode::POLYGON, map);
  } else if (cls == 1) {
    decode_process<T>(dpuout_cls, dpuout_reg, cls, cls_scale, reg_scale,
                      DecodeMode::LANEDIVIDER, map);
  } else {
    throw;
  }
}

template <typename T>
void MapDecoderImpl::decode_process(T const* dpuout_cls, T const* dpuout_reg,
                                    std::uint8_t cls, std::uint8_t cls_scale,
                                    std::uint8_t reg_scale,
                                    DecodeMode decode_mode,
                                    Map& map) noexcept(false) {
  // Input check
  if (nullptr == dpuout_cls || nullptr == dpuout_reg) {
    throw std::runtime_error("Input is null pointer");
    return;
  }

  T start_cls_thres_sig_fix{static_cast<T>(
      std::round(inverse_sigmoid(parameter_.start_cls_threshold.at(cls)) *
                 pow(2, cls_scale)))};
  std::uint32_t const step{net_height_ * net_width_};
  auto& multi_line{parameter_.multi_line.at(cls)};
  auto& preds_number{parameter_.preds_number};
  auto& min_pts_num{parameter_.min_pts_num.at(cls)};

  MapPointsElement cur_points_element;
  auto& lane_divider_joint = map.lane_divider_joint;

  for (std::uint32_t i{detection_width_lower_bound_};
       i < detection_width_upper_bound_; ++i) {
    for (std::uint32_t j{detection_height_lower_bound_};
         j < detection_height_upper_bound_; ++j) {
      std::uint32_t index{j * net_width_ + i};
      for (std::uint32_t k{0U}; k < preds_number; ++k) {
        std::uint32_t cur_index = k * step + index;
        const float& score = dpuout_cls[cur_index];

        if (visited_.at(multi_line * k * step + index) ||
            (score < start_cls_thres_sig_fix))
          continue;

        // finding a search start point
        cur_points_element.points.clear();
        std::memset(&cur_points_element, 0, sizeof(cur_points_element));

        GridPosition start_position{.x = i, .y = j, .pos = k, .reg = 0};
        if (decode_mode == DecodeMode::POLYLINE) {
          search_polyline<T>(dpuout_cls, dpuout_reg, cls, cls_scale, reg_scale,
                             start_position, cur_points_element);
        } else if (decode_mode == DecodeMode::POLYGON) {
          search_polygon<T>(dpuout_cls, dpuout_reg, cls, cls_scale, reg_scale,
                            start_position, cur_points_element);
        } else if (decode_mode == DecodeMode::LANEDIVIDER) {
          search_lane_divider<T>(dpuout_cls, dpuout_reg, cls, cls_scale,
                                 reg_scale, start_position, cur_points_element,
                                 lane_divider_joint);
        } else {
          throw std::runtime_error("Unknown decode mode");
        }

        // remove too short results
        if (cls == 0 || cls == 1 || cls == 2) {
          if (cur_points_element.points.size() < min_pts_num) {
            std::memset(&cur_points_element, 0, sizeof(cur_points_element));
            continue;
          }
        }
        set_result(map, cur_points_element, cls);
      }
    }
  }
  post_process(map, cls);
}

void MapDecoderImpl::set_result(Map& map, MapPointsElement& cur_points_element,
                                std::uint8_t cls) noexcept(false) {
  if (cls == 0) {
    if (map.road_edge.full()) return;
    auto& road_edge = map.road_edge;
    std::size_t number = road_edge.size();
    road_edge.resize(number + 1);

    road_edge.at(number).points = cur_points_element.points;
    road_edge.at(number).category = RoadEdgeCategory::UNKNOWN;
  } else if (cls == 1) {
    if (map.lane_divider.full()) return;
    auto& lane_divider = map.lane_divider;
    std::size_t number = lane_divider.size();
    lane_divider.resize(number + 1);

    lane_divider.at(number).points = cur_points_element.points;
    lane_divider.at(number).line_type = LaneDividerLineType::UNKNOWN;
  } else if (cls == 2) {
    if (map.center_line.full()) return;
    auto& center_line = map.center_line;
    std::size_t number = center_line.size();
    center_line.resize(number + 1);

    center_line.at(number).points = cur_points_element.points;
  } else if (cls == 3) {
    if (map.road_marking.full()) return;
    auto& road_marking = map.road_marking;
    std::size_t number = road_marking.size();
    road_marking.resize(number + 1);

    road_marking.at(number).points = cur_points_element.points;
    road_marking.at(number).category.clear();
    road_marking.at(number).category.add(RoadMarkingCategory::CROSSWALK);
  } else if (cls == 4) {
    if (map.road_marking.full()) return;
    auto& road_marking = map.road_marking;
    std::size_t number = road_marking.size();
    road_marking.resize(number + 1);

    road_marking.at(number).points = cur_points_element.points;
    road_marking.at(number).category.clear();
    road_marking.at(number).category.add(RoadMarkingCategory::STOP_LINE);
  } else if (cls == 5) {
    if (map.road_marking.full()) return;
    auto& road_marking = map.road_marking;
    std::size_t number = road_marking.size();
    road_marking.resize(number + 1);

    road_marking.at(number).points = cur_points_element.points;
    road_marking.at(number).category.clear();
    road_marking.at(number).category.add(RoadMarkingCategory::SPEED_BUMP);
  } else if (cls == 6) {
    if (map.road_marking.full()) return;
    auto& road_marking = map.road_marking;
    std::size_t number = road_marking.size();
    road_marking.resize(number + 1);

    road_marking.at(number).points = cur_points_element.points;
    road_marking.at(number).category.clear();
    road_marking.at(number).category.add(RoadMarkingCategory::NO_STOP);
  } else if (cls == 7) {
    if (map.road_marking.full()) return;
    auto& road_marking = map.road_marking;
    std::size_t number = road_marking.size();
    road_marking.resize(number + 1);

    road_marking.at(number).points = cur_points_element.points;
    road_marking.at(number).category.clear();
    road_marking.at(number).category.add(RoadMarkingCategory::STRAIGHT);
  } else {
    throw std::runtime_error("Unknown cls type");
  }
}

void MapDecoderImpl::post_process(Map& map, std::uint8_t cls) noexcept(false) {
  auto& merge_threshold{parameter_.merge_threshold.at(cls)};
  if (cls == 0) {
    auto& map_elements{map.road_edge};
    merge_discontinuous_elements(map_elements, merge_threshold);
    for (std::uint32_t i{0U}; i < map_elements.size(); ++i) {
      auto& cur_map_element{map_elements.at(i)};
      line_smooth(cur_map_element);

      float start_x = cur_map_element.points.at(0).x;
      float end_x =
          cur_map_element.points.at(cur_map_element.points.size() - 1).x;
      if (start_x > end_x) {
        std::reverse(cur_map_element.points.begin(),
                     cur_map_element.points.end());
      }
    }
  } else if (cls == 1) {
    auto& map_elements{map.lane_divider};
    merge_discontinuous_elements(map_elements, merge_threshold);
    for (std::uint32_t i{0U}; i < map_elements.size(); ++i) {
      auto& cur_map_element{map_elements.at(i)};
      line_smooth(cur_map_element);

      float start_x = cur_map_element.points.at(0).x;
      float end_x =
          cur_map_element.points.at(cur_map_element.points.size() - 1).x;
      if (start_x > end_x) {
        std::reverse(cur_map_element.points.begin(),
                     cur_map_element.points.end());
      }
    }
  } else if (cls == 2) {
    auto& map_elements{map.center_line};
    merge_discontinuous_elements(map_elements, merge_threshold);
    for (std::uint32_t i{0U}; i < map_elements.size(); ++i) {
      auto& cur_map_element{map_elements.at(i)};
      line_smooth(cur_map_element);

      float start_x = cur_map_element.points.at(0).x;
      float end_x =
          cur_map_element.points.at(cur_map_element.points.size() - 1).x;
      if (start_x > end_x) {
        std::reverse(cur_map_element.points.begin(),
                     cur_map_element.points.end());
      }
    }
  } else {
    return;
  }
};

template <typename T>
void MapDecoderImpl::search_line_one_direction_with_joint(
    T const* dpuout_cls, T const* dpuout_reg, std::uint8_t cls,
    std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position, MapPointsElement& cur_map_element,
    MapElementSet<LaneDividerJoint>& lane_divider_joint) noexcept(false) {
  // Input check
  if (nullptr == dpuout_cls || nullptr == dpuout_reg) {
    throw std::runtime_error("Input is null pointer");
    return;
  }
  if (cls != 1) {
    throw std::runtime_error("cls type is not lane divider");
    return;
  }

  GridPosition next_position{.x = start_position.x,
                             .y = start_position.y,
                             .pos = start_position.pos,
                             .reg = start_position.reg};
  T const cls_thres_sig_fix{static_cast<T>(std::round(
      inverse_sigmoid(parameter_.cls_threshold.at(cls)) * pow(2, cls_scale)))};
  T const keypoint_thres_sig_fix{static_cast<T>(
      std::round(inverse_sigmoid(parameter_.keypoint_threshold.at(cls)) *
                 pow(2, cls_scale)))};
  std::uint32_t const step{net_height_ * net_width_};
  auto& multi_line{parameter_.multi_line.at(cls)};
  auto& distance_threshold{parameter_.dis_threshold.at(cls)};
  auto& dir_threshold{parameter_.vec_threshold.at(cls)};

  auto& points = cur_map_element.points;
  float cur_dir_x, cur_dir_y, next_dir_x, next_dir_y, min_xy_x, min_xy_y,
      min_k_xy_x, min_k_xy_y, min_keypoint_score;
  std::uint64_t cur_index, cur_cls_index;
  while (true) {
    std::uint32_t cur_x = next_position.x;
    std::uint32_t cur_y = next_position.y;
    std::uint32_t cur_pos = next_position.pos;
    std::uint32_t cur_reg = next_position.reg;

    cur_index = cur_y * net_width_ + cur_x;
    cur_cls_index = cur_pos * step + cur_index;
    const auto& cur_score{dpuout_cls[cur_cls_index]};
    if (points.full()) {
      return;
    }
    if (visited_.at(multi_line * cur_pos * step + cur_index)) return;
    visited_.at(multi_line * cur_pos * step + cur_index) = 1U;

    // get current grid's direction and xy
    auto const& cur_xy_x =
        dpuout_xy_[(4 * cur_pos + 2 * cur_reg + 0) * step + cur_index];
    auto const& cur_xy_y =
        dpuout_xy_[(4 * cur_pos + 2 * cur_reg + 1) * step + cur_index];

    if ((cur_xy_x - (-1.0f) < EPS) && (cur_xy_y - (-1.0f) < EPS)) {
      decode_grid(dpuout_reg, reg_scale, cur_x, cur_y, cur_pos);
    }
    cur_dir_x = 2.0f * (cur_reg - 0.5) *
                dpuout_vec_[(2 * cur_pos + 0) * step + cur_index];
    cur_dir_y = 2.0f * (cur_reg - 0.5) *
                dpuout_vec_[(2 * cur_pos + 1) * step + cur_index];

    bool found{false};
    float min_loss{std::numeric_limits<float>::max()};
    std::vector<Point3f> target_grids{
        get_target_grids(cur_dir_x, cur_dir_y, cur_xy_x, cur_xy_y)};
    // traverse n closest adjacent grids
    for (std::uint32_t i{0}; i < target_grids.size(); ++i) {
      int next_x = cur_x + target_grids[i].x;
      int next_y = cur_y + target_grids[i].y;
      if (next_x < detection_width_lower_bound_ ||
          next_x >= detection_width_upper_bound_ ||
          next_y < detection_height_lower_bound_ ||
          next_y >= detection_height_upper_bound_)
        continue;
      for (std::uint32_t j{0}; j < parameter_.preds_number; ++j) {
        std::uint64_t next_index{next_y * net_width_ + next_x};
        std::uint64_t next_cls_index{j * step + next_index};
        auto const& next_score{dpuout_cls[next_cls_index]};
        auto& is_visited{visited_.at(multi_line * j * step + next_index)};
        if (next_score < cls_thres_sig_fix || is_visited) continue;

        // traverse two points in each prediction
        for (std::uint32_t k{0}; k < 2; ++k) {
          // get next grid's direction and xy
          auto const& next_xy_x =
              dpuout_xy_[(4 * j + 2 * k + 0) * step + next_index];
          auto const& next_xy_y =
              dpuout_xy_[(4 * j + 2 * k + 1) * step + next_index];
          auto const& next_k_xy_x =
              dpuout_xy_[(4 * j + 2 * (1 - k) + 0) * step + next_index];
          auto const& next_k_xy_y =
              dpuout_xy_[(4 * j + 2 * (1 - k) + 1) * step + next_index];

          if ((next_xy_x - (-1.0f) < EPS) && (next_xy_y - (-1.0f) < EPS)) {
            decode_grid(dpuout_reg, reg_scale, next_x, next_y, j);
          }
          next_dir_x = -1.0 * 2.0 * (k - 0.5) *
                       dpuout_vec_[(2 * j + 0) * step + next_index];
          next_dir_y = -1.0 * 2.0 * (k - 0.5) *
                       dpuout_vec_[(2 * j + 1) * step + next_index];
          auto const& next_keypoint_score{dpuout_cls[2 * step + next_index]};

          // calculate distance loss and direction loss
          float distance{get_distance(cur_xy_x, cur_xy_y,
                                      next_xy_x + target_grids[i].x,
                                      next_xy_y + target_grids[i].y)};
          float theta{get_theta(cur_dir_x, cur_dir_y, next_dir_x, next_dir_y)};
          if (distance > distance_threshold || theta > dir_threshold) continue;
          float cur_loss{distance * parameter_.dis_loss_weight +
                         (theta / dir_threshold) *
                             (1 - parameter_.dis_loss_weight)};

          // find min loss
          if (cur_loss < min_loss) {
            found = true;
            min_loss = cur_loss;
            next_position.x = next_x;
            next_position.y = next_y;
            next_position.pos = j;
            next_position.reg = 1 - k;

            min_xy_x = next_xy_x;
            min_xy_y = next_xy_y;
            min_k_xy_x = next_k_xy_x;
            min_k_xy_y = next_k_xy_y;

            min_keypoint_score = next_keypoint_score;
          }
        }
      }
    }
    // save current points to array
    MapElementPoint pts;
    std::memset(&pts, 0, sizeof(pts));
    pts.z = 0.0;
    pts.score = sigmoid(cur_score / powf(2.0f, cls_scale));
    if (cls == 1) {  // lane_divider
      float cur_type =
          dpuout_cls[(3 + cur_pos) * step + cur_index] / powf(2.0f, cls_scale);
      pts.reserve[0] = sigmoid(cur_type);
    } else if (cls == 7) {  // turn
      for (std::uint32_t turn_index{0}; turn_index < 6; ++turn_index) {
        float cur_turn_type = dpuout_cls[(2 + turn_index) * step + cur_index] /
                              powf(2.0f, cls_scale);
        pts.reserve[turn_index] = sigmoid(cur_turn_type);
      }
    }

    if (found) {
      auto& next_x{next_position.x};
      auto& next_y{next_position.y};
      auto& next_pos{next_position.pos};
      auto& next_reg{next_position.reg};

      pts.x = (0.8f * (cur_x + cur_xy_x) + 0.2f * (next_x + min_xy_x)) *
                  parameter_.voxel_size.at(0) * parameter_.down_size +
              parameter_.net_range.at(0);
      pts.y = (0.8f * (cur_y + cur_xy_y) + 0.2f * (next_y + min_xy_y)) *
                  parameter_.voxel_size.at(1) * parameter_.down_size +
              parameter_.net_range.at(1);
      points.add(pts);

      if (min_keypoint_score > keypoint_thres_sig_fix) {
        LaneDividerJoint new_joint;
        std::memset(&new_joint, 0, sizeof(new_joint));
        new_joint.point.x =
            (0.5f * (next_x + min_k_xy_x) + 0.5f * (next_x + min_xy_x)) *
                parameter_.voxel_size.at(0) * parameter_.down_size +
            parameter_.net_range.at(0);
        new_joint.point.y =
            (0.5f * (next_y + min_k_xy_y) + 0.5f * (next_y + min_xy_y)) *
                parameter_.voxel_size.at(1) * parameter_.down_size +
            parameter_.net_range.at(1);
        new_joint.point.z = 0.0;
        new_joint.point.score =
            sigmoid(min_keypoint_score / powf(2.0f, cls_scale));
        if (!lane_divider_joint.full()) {
          lane_divider_joint.add(new_joint);
        }

        visited_.at(multi_line * 0 * step + next_y * net_width_ + next_x) = 1U;
        visited_.at(multi_line * 1 * step + next_y * net_width_ + next_x) = 1U;
        return;
      }
    } else {
      pts.x = (cur_x + cur_xy_x) * parameter_.voxel_size.at(0) *
                  parameter_.down_size +
              parameter_.net_range.at(0);
      pts.y = (cur_y + cur_xy_y) * parameter_.voxel_size.at(1) *
                  parameter_.down_size +
              parameter_.net_range.at(1);
      points.add(pts);
      return;
    }

    int next_next_x = next_position.x;
    int next_next_y = next_position.y;
    if (std::fabs(min_k_xy_x - 1.0f) < EPS) {
      next_next_x += 1.0f;
    } else if (std::fabs(min_k_xy_x - 0.0f) < EPS) {
      next_next_x += -1.0f;
    } else if (std::fabs(min_k_xy_y - 1.0f) < EPS) {
      next_next_y += 1.0f;
    } else if (std::fabs(min_k_xy_y - 0.0f) < EPS) {
      next_next_y += -1.0f;
    } else {
      throw std::runtime_error("Wrong Direction!");
    }

    if (!multi_line) {
      for (std::uint32_t i{0}; i < target_grids.size(); ++i) {
        int next_x = cur_x + target_grids[i].x;
        int next_y = cur_y + target_grids[i].y;
        if (next_x < detection_width_lower_bound_ ||
            next_x >= detection_width_upper_bound_ ||
            next_y < detection_height_lower_bound_ ||
            next_y >= detection_height_upper_bound_)
          continue;
        if ((next_x == next_next_x && next_y == next_next_y) ||
            (next_x == next_position.x && next_y == next_position.y))
          continue;
        visited_.at(next_y * net_width_ + next_x) = 1U;
      }
    }
  }
}

template <typename T>
void MapDecoderImpl::search_line_one_direction(
    T const* dpuout_cls, T const* dpuout_reg, std::uint8_t cls,
    std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position,
    MapPointsElement& cur_map_element) noexcept(false) {
  // Input check
  if (nullptr == dpuout_cls || nullptr == dpuout_reg) {
    throw std::runtime_error("Input is null pointer");
    return;
  }

  GridPosition next_position{.x = start_position.x,
                             .y = start_position.y,
                             .pos = start_position.pos,
                             .reg = start_position.reg};
  T const cls_thres_sig_fix{static_cast<T>(std::round(
      inverse_sigmoid(parameter_.cls_threshold.at(cls)) * pow(2, cls_scale)))};
  std::uint32_t const step{net_height_ * net_width_};
  auto& multi_line{parameter_.multi_line.at(cls)};
  auto& distance_threshold{parameter_.dis_threshold.at(cls)};
  auto& dir_threshold{parameter_.vec_threshold.at(cls)};

  auto& points = cur_map_element.points;
  float cur_dir_x, cur_dir_y, next_dir_x, next_dir_y, min_xy_x, min_xy_y,
      min_k_xy_x, min_k_xy_y;
  std::uint64_t cur_index, cur_cls_index;
  while (true) {
    std::uint32_t cur_x = next_position.x;
    std::uint32_t cur_y = next_position.y;
    std::uint32_t cur_pos = next_position.pos;
    std::uint32_t cur_reg = next_position.reg;

    cur_index = cur_y * net_width_ + cur_x;
    cur_cls_index = cur_pos * step + cur_index;
    const auto& cur_score{dpuout_cls[cur_cls_index]};
    if (points.full()) {
      return;
    }
    if (visited_.at(multi_line * cur_pos * step + cur_index)) return;
    visited_.at(multi_line * cur_pos * step + cur_index) = 1U;

    // get current grid's direction and xy
    auto const& cur_xy_x =
        dpuout_xy_[(4 * cur_pos + 2 * cur_reg + 0) * step + cur_index];
    auto const& cur_xy_y =
        dpuout_xy_[(4 * cur_pos + 2 * cur_reg + 1) * step + cur_index];

    if ((cur_xy_x - (-1.0f) < EPS) && (cur_xy_y - (-1.0f) < EPS)) {
      decode_grid(dpuout_reg, reg_scale, cur_x, cur_y, cur_pos);
    }
    cur_dir_x = 2.0f * (cur_reg - 0.5) *
                dpuout_vec_[(2 * cur_pos + 0) * step + cur_index];
    cur_dir_y = 2.0f * (cur_reg - 0.5) *
                dpuout_vec_[(2 * cur_pos + 1) * step + cur_index];

    bool found{false};
    float min_loss{std::numeric_limits<float>::max()};
    std::vector<Point3f> target_grids{
        get_target_grids(cur_dir_x, cur_dir_y, cur_xy_x, cur_xy_y)};
    // traverse n closest adjacent grids
    for (std::uint32_t i{0}; i < target_grids.size(); ++i) {
      int next_x = cur_x + target_grids[i].x;
      int next_y = cur_y + target_grids[i].y;
      if (next_x < detection_width_lower_bound_ ||
          next_x >= detection_width_upper_bound_ ||
          next_y < detection_height_lower_bound_ ||
          next_y >= detection_height_upper_bound_)
        continue;
      for (std::uint32_t j{0}; j < parameter_.preds_number; ++j) {
        std::uint64_t next_index{next_y * net_width_ + next_x};
        std::uint64_t next_cls_index{j * step + next_index};
        auto const& next_score{dpuout_cls[next_cls_index]};
        auto& is_visited{visited_.at(multi_line * j * step + next_index)};
        if (next_score < cls_thres_sig_fix || is_visited) continue;

        // traverse two points in each prediction
        for (std::uint32_t k{0}; k < 2; ++k) {
          // get next grid's direction and xy
          auto const& next_xy_x =
              dpuout_xy_[(4 * j + 2 * k + 0) * step + next_index];
          auto const& next_xy_y =
              dpuout_xy_[(4 * j + 2 * k + 1) * step + next_index];
          auto const& next_k_xy_x =
              dpuout_xy_[(4 * j + 2 * (1 - k) + 0) * step + next_index];
          auto const& next_k_xy_y =
              dpuout_xy_[(4 * j + 2 * (1 - k) + 1) * step + next_index];

          if ((next_xy_x - (-1.0f) < EPS) && (next_xy_y - (-1.0f) < EPS)) {
            decode_grid(dpuout_reg, reg_scale, next_x, next_y, j);
          }
          next_dir_x = -1.0 * 2.0 * (k - 0.5) *
                       dpuout_vec_[(2 * j + 0) * step + next_index];
          next_dir_y = -1.0 * 2.0 * (k - 0.5) *
                       dpuout_vec_[(2 * j + 1) * step + next_index];

          // calculate distance loss and direction loss
          float distance{get_distance(cur_xy_x, cur_xy_y,
                                      next_xy_x + target_grids[i].x,
                                      next_xy_y + target_grids[i].y)};
          float theta{get_theta(cur_dir_x, cur_dir_y, next_dir_x, next_dir_y)};
          if (distance > distance_threshold || theta > dir_threshold) continue;
          float cur_loss{distance * parameter_.dis_loss_weight +
                         (theta / dir_threshold) *
                             (1 - parameter_.dis_loss_weight)};

          // find min loss
          if (cur_loss < min_loss) {
            found = true;
            min_loss = cur_loss;
            next_position.x = next_x;
            next_position.y = next_y;
            next_position.pos = j;
            next_position.reg = 1 - k;

            min_xy_x = next_xy_x;
            min_xy_y = next_xy_y;
            min_k_xy_x = next_k_xy_x;
            min_k_xy_y = next_k_xy_y;
          }
        }
      }
    }
    // save current points to array
    MapElementPoint pts;
    std::memset(&pts, 0, sizeof(pts));
    pts.z = 0.0;
    pts.score = sigmoid(cur_score / powf(2.0f, cls_scale));
    if (cls == 1) {  // lane_divider
      float cur_type =
          dpuout_cls[(3 + cur_pos) * step + cur_index] / powf(2.0f, cls_scale);
      pts.reserve[0] = sigmoid(cur_type);
    } else if (cls == 7) {  // turn
      for (std::uint32_t turn_index{0}; turn_index < 6; ++turn_index) {
        float cur_turn_type = dpuout_cls[(2 + turn_index) * step + cur_index] /
                              powf(2.0f, cls_scale);
        pts.reserve[turn_index] = sigmoid(cur_turn_type);
      }
    }

    if (found) {
      auto& next_x{next_position.x};
      auto& next_y{next_position.y};
      auto& next_pos{next_position.pos};
      auto& next_reg{next_position.reg};

      pts.x = (0.8f * (cur_x + cur_xy_x) + 0.2f * (next_x + min_xy_x)) *
                  parameter_.voxel_size.at(0) * parameter_.down_size +
              parameter_.net_range.at(0);
      pts.y = (0.8f * (cur_y + cur_xy_y) + 0.2f * (next_y + min_xy_y)) *
                  parameter_.voxel_size.at(1) * parameter_.down_size +
              parameter_.net_range.at(1);
      points.add(pts);
    } else {
      pts.x = (cur_x + cur_xy_x) * parameter_.voxel_size.at(0) *
                  parameter_.down_size +
              parameter_.net_range.at(0);
      pts.y = (cur_y + cur_xy_y) * parameter_.voxel_size.at(1) *
                  parameter_.down_size +
              parameter_.net_range.at(1);
      points.add(pts);
      return;
    }

    int next_next_x = next_position.x;
    int next_next_y = next_position.y;
    if (std::fabs(min_k_xy_x - 1.0f) < EPS) {
      next_next_x += 1.0f;
    } else if (std::fabs(min_k_xy_x - 0.0f) < EPS) {
      next_next_x += -1.0f;
    } else if (std::fabs(min_k_xy_y - 1.0f) < EPS) {
      next_next_y += 1.0f;
    } else if (std::fabs(min_k_xy_y - 0.0f) < EPS) {
      next_next_y += -1.0f;
    } else {
      throw std::runtime_error("Wrong Direction!");
    }

    if (!multi_line) {
      for (std::uint32_t i{0}; i < target_grids.size(); ++i) {
        int next_x = cur_x + target_grids[i].x;
        int next_y = cur_y + target_grids[i].y;
        if (next_x < detection_width_lower_bound_ ||
            next_x >= detection_width_upper_bound_ ||
            next_y < detection_height_lower_bound_ ||
            next_y >= detection_height_upper_bound_)
          continue;
        if ((next_x == next_next_x && next_y == next_next_y) ||
            (next_x == next_position.x && next_y == next_position.y))
          continue;
        visited_.at(next_y * net_width_ + next_x) = 1U;
      }
    }
  }
}

template <typename T>
void MapDecoderImpl::search_polyline(
    T const* dpuout_cls, T const* dpuout_reg, std::uint8_t cls,
    std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position,
    MapPointsElement& cur_map_element) noexcept(false) {
  // search 1st direction
  search_line_one_direction<T>(dpuout_cls, dpuout_reg, cls, cls_scale,
                               reg_scale, start_position, cur_map_element);
  // reverse in order to concatenate two directions
  std::reverse(cur_map_element.points.begin(), cur_map_element.points.end());
  // search 2nd direction
  start_position.reg = 1;
  visited_.at(parameter_.multi_line.at(cls) * start_position.pos * net_height_ *
                  net_width_ +
              start_position.y * net_width_ + start_position.x) = 0U;
  search_line_one_direction<T>(dpuout_cls, dpuout_reg, cls, cls_scale,
                               reg_scale, start_position, cur_map_element);
}

template <typename T>
void MapDecoderImpl::search_polygon(
    T const* dpuout_cls, T const* dpuout_reg, std::uint8_t cls,
    std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position,
    MapPointsElement& cur_map_element) noexcept(false) {
  // Input check
  if (nullptr == dpuout_cls || nullptr == dpuout_reg) {
    throw std::runtime_error("Input is null pointer");
    return;
  }

  T const cls_thres_sig_fix{static_cast<T>(std::round(
      inverse_sigmoid(parameter_.cls_threshold.at(cls)) * pow(2, cls_scale)))};
  std::uint32_t const step{net_height_ * net_width_};
  auto& multi_line{parameter_.multi_line.at(cls)};

  std::queue<GridPosition> queue;
  queue.push(start_position);

  auto& points{cur_map_element.points};
  std::uint64_t cur_index, cur_cls_index;
  while (!queue.empty()) {
    auto cur_position = queue.front();
    queue.pop();

    std::uint32_t cur_x = cur_position.x;
    std::uint32_t cur_y = cur_position.y;
    std::uint32_t cur_pos = cur_position.pos;

    cur_index = cur_y * net_width_ + cur_x;
    cur_cls_index = cur_pos * step + cur_index;
    const auto& cur_score = dpuout_cls[cur_cls_index];
    if (cur_map_element.points.full()) {
      return;
    }

    if (visited_.at(multi_line * cur_pos * step + cur_index)) continue;
    visited_.at(multi_line * cur_pos * step + cur_index) = 1U;

    // get current grid's direction and xy
    auto const& cur_xy_x =
        dpuout_xy_[(4 * cur_pos + 2 * 0 + 0) * step + cur_index];
    auto const& cur_xy_y =
        dpuout_xy_[(4 * cur_pos + 2 * 0 + 1) * step + cur_index];
    auto const& cur_k_xy_x =
        dpuout_xy_[(4 * cur_pos + 2 * 1 + 0) * step + cur_index];
    auto const& cur_k_xy_y =
        dpuout_xy_[(4 * cur_pos + 2 * 1 + 1) * step + cur_index];

    if ((cur_xy_x - (-1.0f) < EPS) && (cur_xy_y - (-1.0f) < EPS)) {
      decode_grid(dpuout_reg, reg_scale, cur_x, cur_y, cur_pos);
    }

    for (int i{-1}; i < 2; ++i) {
      for (int j{-1}; j < 2; ++j) {
        if (i == 0 && j == 0) continue;

        int next_x = cur_x + i;
        int next_y = cur_y + j;

        if (next_x < detection_width_lower_bound_ ||
            next_x >= detection_width_upper_bound_ ||
            next_y < detection_height_lower_bound_ ||
            next_y >= detection_height_upper_bound_)
          continue;

        std::uint64_t next_k0_cls_index =
            0 * step + next_y * net_width_ + next_x;
        std::uint64_t next_k1_cls_index =
            1 * step + next_y * net_width_ + next_x;

        auto& next_k0_score = dpuout_cls[next_k0_cls_index];
        auto& next_k1_score = dpuout_cls[next_k1_cls_index];

        std::uint64_t next_cls_index;
        std::uint8_t next_k;
        if (next_k0_score > next_k1_score) {
          next_cls_index = next_k0_cls_index;
          next_k = 0;
        } else {
          next_cls_index = next_k1_cls_index;
          next_k = 1;
        }

        auto const& next_score = dpuout_cls[next_cls_index];
        auto& is_visited = visited_.at(multi_line * next_k * step +
                                       next_y * net_width_ + next_x);
        if (next_score < cls_thres_sig_fix || is_visited) continue;
        queue.push(GridPosition{.x = static_cast<uint32_t>(next_x),
                                .y = static_cast<uint32_t>(next_y),
                                .pos = next_k,
                                .reg = 0});
      }
    }

    // save current points to array
    MapElementPoint pts0;
    std::memset(&pts0, 0, sizeof(pts0));
    pts0.x = (cur_x + cur_xy_x) * parameter_.voxel_size.at(0) *
                 parameter_.down_size +
             parameter_.net_range.at(0);
    pts0.y = (cur_y + cur_xy_y) * parameter_.voxel_size.at(1) *
                 parameter_.down_size +
             parameter_.net_range.at(1);
    pts0.z = 0.0;
    pts0.score = sigmoid(cur_score / powf(2.0f, cls_scale));
    if (cls == 1) {  // lane_divider
      float cur_type =
          dpuout_cls[(3 + cur_pos) * step + cur_y * net_width_ + cur_x] /
          powf(2.0f, cls_scale);
      pts0.reserve[0] = sigmoid(cur_type);
    } else if (cls == 7) {  // turn
      for (std::uint32_t turn_index{0}; turn_index < 6; ++turn_index) {
        float cur_turn_type =
            dpuout_cls[(2 + turn_index) * step + cur_y * net_width_ + cur_x] /
            powf(2.0f, cls_scale);
        pts0.reserve[turn_index] = sigmoid(cur_turn_type);
      }
    }
    points.add(pts0);

    if (points.full()) return;

    MapElementPoint pts1{pts0};
    pts1.x = (cur_x + cur_k_xy_x) * parameter_.voxel_size.at(0) *
                 parameter_.down_size +
             parameter_.net_range.at(0);
    pts1.y = (cur_y + cur_k_xy_y) * parameter_.voxel_size.at(1) *
                 parameter_.down_size +
             parameter_.net_range.at(1);
    points.add(pts1);
  }
}

template <typename T>
void MapDecoderImpl::search_lane_divider(
    T const* dpuout_cls, T const* dpuout_reg, std::uint8_t cls,
    std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position, MapPointsElement& cur_map_element,
    MapElementSet<LaneDividerJoint>& lane_divider_joint) noexcept(false) {
  // search 1st direction
  search_line_one_direction_with_joint<T>(dpuout_cls, dpuout_reg, cls,
                                          cls_scale, reg_scale, start_position,
                                          cur_map_element, lane_divider_joint);
  // reverse in order to concatenate two directions
  std::reverse(cur_map_element.points.begin(), cur_map_element.points.end());
  // search 2nd direction
  start_position.reg = 1;
  visited_.at(parameter_.multi_line.at(cls) * start_position.pos * net_height_ *
                  net_width_ +
              start_position.y * net_width_ + start_position.x) = 0U;
  search_line_one_direction_with_joint<T>(dpuout_cls, dpuout_reg, cls,
                                          cls_scale, reg_scale, start_position,
                                          cur_map_element, lane_divider_joint);
}

bool MapDecoderImpl::merge_two_line_segments(
    MapPointsElement& line1, MapPointsElement& line2,
    float const merge_threshold) noexcept {
  if ((line1.points.size() == 0 || line2.points.size() == 0)) return false;
  auto& start_pts_1{line1.points.at(0)};
  auto& end_pts_1{line1.points.at(line1.points.size() - 1)};
  auto& start_pts_2{line2.points.at(0)};
  auto& end_pts_2{line2.points.at(line2.points.size() - 1)};

  if (get_distance_square(start_pts_1.x, start_pts_1.y, start_pts_2.x,
                          start_pts_2.y) < merge_threshold) {
    std::reverse(line1.points.begin(), line1.points.end());
  } else if (get_distance_square(start_pts_1.x, start_pts_1.y, end_pts_2.x,
                                 end_pts_2.y) < merge_threshold) {
    std::reverse(line1.points.begin(), line1.points.end());
    std::reverse(line2.points.begin(), line2.points.end());
  } else if (get_distance_square(end_pts_1.x, end_pts_1.y, start_pts_2.x,
                                 start_pts_2.y) < merge_threshold) {
  } else if (get_distance_square(end_pts_1.x, end_pts_1.y, end_pts_2.x,
                                 end_pts_2.y) < merge_threshold) {
    std::reverse(line2.points.begin(), line2.points.end());
  } else {
    return false;
  }
  for (int i{1}; i < line2.points.size(); i++) {
    if (line1.points.full()) break;
    line1.points.add(line2.points.at(i));
  }
  return true;
}

void MapDecoderImpl::line_smooth(MapPointsElement& line) noexcept {
  auto const& filled_size = line.points.size();
  if (filled_size < 5) return;

  for (std::uint16_t i{0}; i < filled_size; i++) {
    auto& cur_pts{line.points.at(i)};
    if (i == 0) {
      cur_pts.x = (3.0f * line.points.at(0).x + 2.0f * line.points.at(1).x +
                   line.points.at(2).x - line.points.at(4).x) /
                  5.0f;
      cur_pts.y = (3.0f * line.points.at(0).y + 2.0f * line.points.at(1).y +
                   line.points.at(2).y - line.points.at(4).y) /
                  5.0f;
    } else if (i == 1) {
      cur_pts.x = (4.0f * line.points.at(0).x + 3.0f * line.points.at(1).x +
                   2.0f * line.points.at(2).x + line.points.at(3).x) /
                  10.0f;
      cur_pts.y = (4.0f * line.points.at(0).y + 3.0f * line.points.at(1).y +
                   2.0f * line.points.at(2).y + line.points.at(3).y) /
                  10.0f;
    } else if (i == filled_size - 2) {
      cur_pts.x = (4.0f * line.points.at(filled_size - 1).x +
                   3.0f * line.points.at(filled_size - 2).x +
                   2.0f * line.points.at(filled_size - 3).x +
                   line.points.at(filled_size - 4).x) /
                  10.0f;
      cur_pts.y = (4.0f * line.points.at(filled_size - 1).y +
                   3.0f * line.points.at(filled_size - 2).y +
                   2.0f * line.points.at(filled_size - 3).y +
                   line.points.at(filled_size - 4).y) /
                  10.0f;
    } else if (i == filled_size - 1) {
      cur_pts.x = (3.0f * line.points.at(filled_size - 1).x +
                   2.0f * line.points.at(filled_size - 2).x +
                   1.0f * line.points.at(filled_size - 3).x -
                   line.points.at(filled_size - 5).x) /
                  5.0f;
      cur_pts.y = (3.0f * line.points.at(filled_size - 1).y +
                   2.0f * line.points.at(filled_size - 2).y +
                   1.0f * line.points.at(filled_size - 3).y -
                   line.points.at(filled_size - 5).y) /
                  5.0f;
    } else {
      cur_pts.x = (line.points.at(i - 2).x + line.points.at(i - 1).x +
                   line.points.at(i).x + line.points.at(i + 1).x +
                   line.points.at(i + 2).x) /
                  5.0f;
      cur_pts.y = (line.points.at(i - 2).y + line.points.at(i - 1).y +
                   line.points.at(i).y + line.points.at(i + 1).y +
                   line.points.at(i + 2).y) /
                  5.0f;
    }
  }
}

template <typename T>
void MapDecoderImpl::decode_grid(T const* dpuout_reg,
                                 const std::uint8_t reg_scale,
                                 std::uint32_t const x, std::uint32_t const y,
                                 std::uint32_t const pos) noexcept(false) {
  if (nullptr == dpuout_reg) {
    throw std::runtime_error("Input is null pointer!");
    return;
  }

  std::uint32_t const step{net_height_ * net_width_};
  std::uint32_t index{y * net_width_ + x};
  std::uint64_t cur_reg_index{
      static_cast<std::uint64_t>(2 * pos * step + index)};

  float reg_cw{static_cast<float>(dpuout_reg[cur_reg_index]) /
               powf(2.0f, reg_scale)};
  float reg_ccw{static_cast<float>(dpuout_reg[cur_reg_index + step]) /
                powf(2.0f, reg_scale)};

  Point3f cw_xy{distance_decode(reg_cw, true)};
  Point3f ccw_xy{distance_decode(reg_ccw, false)};
  Point3f vec{get_vec(cw_xy.x, cw_xy.y, ccw_xy.x, ccw_xy.y)};

  dpuout_xy_[static_cast<std::uint64_t>((4 * pos + 0) * step + index)] =
      cw_xy.x;
  dpuout_xy_[static_cast<std::uint64_t>((4 * pos + 1) * step + index)] =
      cw_xy.y;
  dpuout_xy_[static_cast<std::uint64_t>((4 * pos + 2) * step + index)] =
      ccw_xy.x;
  dpuout_xy_[static_cast<std::uint64_t>((4 * pos + 3) * step + index)] =
      ccw_xy.y;

  dpuout_vec_[static_cast<std::uint64_t>((2 * pos + 0) * step + index)] = vec.x;
  dpuout_vec_[static_cast<std::uint64_t>((2 * pos + 1) * step + index)] = vec.y;
}

inline Point3f MapDecoderImpl::distance_decode(float const distance,
                                               bool const is_cw) noexcept {
  Point3f xy;
  float cw_distance{distance};
  while (cw_distance < 0) {
    cw_distance += 4.0f;
  }
  while (cw_distance > 4.0f) {
    cw_distance -= 4.0f;
  }

  if (!is_cw) {
    cw_distance = 4 - cw_distance;
  }
  if (cw_distance >= 0 && cw_distance <= 1) {
    xy.x = 0.0;
    xy.y = cw_distance;
  } else if (cw_distance > 1 && cw_distance <= 2) {
    xy.x = cw_distance - 1;
    xy.y = 1.0;
  } else if (cw_distance > 2 && cw_distance <= 3) {
    xy.x = 1.0;
    xy.y = 3 - cw_distance;
  } else if (cw_distance > 3 && cw_distance <= 4) {
    xy.x = 4 - cw_distance;
    xy.y = 0.0;
  } else {
    xy.x = 0.0;
    xy.y = -cw_distance;
  }
  return xy;
}

inline Point3f MapDecoderImpl::get_vec(float x0, float y0, float x1,
                                       float y1) noexcept {
  Point3f dir;
  float x = x1 - x0;
  float y = y1 - y0;
  float l = get_distance(x0, y0, x1, y1);

  dir.x = x / (l + EPS);
  dir.y = y / (l + EPS);
  return dir;
}

inline std::vector<Point3f> MapDecoderImpl::get_target_grids(
    float cur_dir_x, float cur_dir_y, float cur_xy_x,
    float cur_xy_y) noexcept(false) {
  std::vector<Point3f> target_grids(3);
  if (std::fabs(std::fabs(cur_dir_x) - 1.0f) < EPS ||
      std::fabs(std::fabs(cur_dir_y) - 1.0f) < EPS) {
    if (std::fabs(cur_dir_x - 1.0f) < EPS) {
      target_grids[0] = autodrive::perception::Point3f{1.0f, 0.0f, 0.0f};
      target_grids[1] = autodrive::perception::Point3f{1.0f, 1.0f, 0.0f};
      target_grids[2] = autodrive::perception::Point3f{1.0f, -1.0f, 0.0f};
    } else if (std::fabs(cur_dir_x - (-1.0f)) < EPS) {
      target_grids[0] = autodrive::perception::Point3f{-1.0f, 0.0f, 0.0f};
      target_grids[1] = autodrive::perception::Point3f{-1.0f, 1.0f, 0.0f};
      target_grids[2] = autodrive::perception::Point3f{-1.0f, -1.0f, 0.0f};
    } else if (std::fabs(cur_dir_y - 1.0f) < EPS) {
      target_grids[0] = autodrive::perception::Point3f{0.0f, 1.0f, 0.0f};
      target_grids[1] = autodrive::perception::Point3f{1.0f, 1.0f, 0.0f};
      target_grids[2] = autodrive::perception::Point3f{-1.0f, 1.0f, 0.0f};
    } else if (std::fabs(cur_dir_y - (-1.0f)) < EPS) {
      target_grids[0] = autodrive::perception::Point3f{0.0f, -1.0f, 0.0f};
      target_grids[1] = autodrive::perception::Point3f{1.0f, -1.0f, 0.0f};
      target_grids[2] = autodrive::perception::Point3f{-1.0f, -1.0f, 0.0f};
    } else {
      throw std::runtime_error("Wrong direction");
    }
  } else {
    if (std::fabs(cur_xy_x - 1.0f) < EPS) {
      target_grids[0] = autodrive::perception::Point3f{1.0f, 0.0f, 0.0f};
      target_grids[1] = autodrive::perception::Point3f{1.0f, 1.0f, 0.0f};
      target_grids[2] = autodrive::perception::Point3f{1.0f, -1.0f, 0.0f};
    } else if (std::fabs(cur_xy_x - 0.0f) < EPS) {
      target_grids[0] = autodrive::perception::Point3f{-1.0f, 0.0f, 0.0f};
      target_grids[1] = autodrive::perception::Point3f{-1.0f, 1.0f, 0.0f};
      target_grids[2] = autodrive::perception::Point3f{-1.0f, -1.0f, 0.0f};
    } else if (std::fabs(cur_xy_y - 1.0f) < EPS) {
      target_grids[0] = autodrive::perception::Point3f{0.0f, 1.0f, 0.0f};
      target_grids[1] = autodrive::perception::Point3f{1.0f, 1.0f, 0.0f};
      target_grids[2] = autodrive::perception::Point3f{-1.0f, 1.0f, 0.0f};
    } else if (std::fabs(cur_xy_y - 0.0f) < EPS) {
      target_grids[0] = autodrive::perception::Point3f{0.0f, -1.0f, 0.0f};
      target_grids[1] = autodrive::perception::Point3f{1.0f, -1.0f, 0.0f};
      target_grids[2] = autodrive::perception::Point3f{-1.0f, -1.0f, 0.0f};
    } else {
      throw std::runtime_error("Wrong direction");
    }
  }
  return target_grids;
}

template void MapDecoderImpl::MapDecode(
    float const* dpuout_cls, float const* dpuout_reg, std::uint8_t cls,
    std::uint8_t cls_scale, std::uint8_t reg_scale, Map& map) noexcept(false);

template void MapDecoderImpl::MapDecode(std::int16_t const* dpuout_cls,
                                        std::int16_t const* dpuout_reg,
                                        std::uint8_t cls,
                                        std::uint8_t cls_scale,
                                        std::uint8_t reg_scale,
                                        Map& map) noexcept(false);

template void MapDecoderImpl::MapDecode(std::int8_t const* dpuout_cls,
                                        std::int8_t const* dpuout_reg,
                                        std::uint8_t cls,
                                        std::uint8_t cls_scale,
                                        std::uint8_t reg_scale,
                                        Map& map) noexcept(false);

template void MapDecoderImpl::decode_process(
    std::int16_t const* dpuout_cls, std::int16_t const* dpuout_reg,
    std::uint8_t cls, std::uint8_t cls_scale, std::uint8_t reg_scale,
    DecodeMode decode_mode, Map& map) noexcept(false);

template void MapDecoderImpl::decode_process(
    std::int8_t const* dpuout_cls, std::int8_t const* dpuout_reg,
    std::uint8_t cls, std::uint8_t cls_scale, std::uint8_t reg_scale,
    DecodeMode decode_mode, Map& map) noexcept(false);

template void MapDecoderImpl::search_line_one_direction_with_joint(
    std::int8_t const* dpuout_cls, std::int8_t const* dpuout_reg,
    std::uint8_t cls, std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position, MapPointsElement& cur_map_element,
    MapElementSet<LaneDividerJoint>& lane_divider_joint) noexcept(false);

template void MapDecoderImpl::search_line_one_direction_with_joint(
    std::int16_t const* dpuout_cls, std::int16_t const* dpuout_reg,
    std::uint8_t cls, std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position, MapPointsElement& cur_map_element,
    MapElementSet<LaneDividerJoint>& lane_divider_joint) noexcept(false);

template void MapDecoderImpl::search_line_one_direction(
    std::int8_t const* dpuout_cls, std::int8_t const* dpuout_reg,
    std::uint8_t cls, std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position,
    MapPointsElement& cur_map_element) noexcept(false);

template void MapDecoderImpl::search_line_one_direction(
    std::int16_t const* dpuout_cls, std::int16_t const* dpuout_reg,
    std::uint8_t cls, std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position,
    MapPointsElement& cur_map_element) noexcept(false);

template void MapDecoderImpl::search_polyline(
    std::int16_t const* dpuout_cls, std::int16_t const* dpuout_reg,
    std::uint8_t cls, std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position,
    MapPointsElement& cur_map_element) noexcept(false);

template void MapDecoderImpl::search_polyline(
    std::int8_t const* dpuout_cls, std::int8_t const* dpuout_reg,
    std::uint8_t cls, std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position,
    MapPointsElement& cur_map_element) noexcept(false);

template void MapDecoderImpl::search_polygon(
    std::int16_t const* dpuout_cls, std::int16_t const* dpuout_reg,
    std::uint8_t cls, std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position,
    MapPointsElement& cur_map_element) noexcept(false);

template void MapDecoderImpl::search_polygon(
    std::int8_t const* dpuout_cls, std::int8_t const* dpuout_reg,
    std::uint8_t cls, std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position,
    MapPointsElement& cur_map_element) noexcept(false);

template void MapDecoderImpl::search_lane_divider(
    std::int16_t const* dpuout_cls, std::int16_t const* dpuout_reg,
    std::uint8_t cls, std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position, MapPointsElement& cur_map_element,
    MapElementSet<LaneDividerJoint>& lane_divider_joint) noexcept(false);

template void MapDecoderImpl::search_lane_divider(
    std::int8_t const* dpuout_cls, std::int8_t const* dpuout_reg,
    std::uint8_t cls, std::uint8_t cls_scale, std::uint8_t reg_scale,
    GridPosition& start_position, MapPointsElement& cur_map_element,
    MapElementSet<LaneDividerJoint>& lane_divider_joint) noexcept(false);

}  // namespace perception
}  // namespace autodrive